-- Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
-- 
-- this file licensed under the Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
--
-- THIS SOFTWARE IS PROVIDED ON AN \"AS IS\" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
-- PURPOSE.
-- See the Mulan PSL v2 for more details.

local log = require 'mc.logging'
local class = require 'mc.class'
local Singleton = require 'mc.singleton'
local props = require 'basic_cooling.define.cooling_properties'
local cooling_enums = require 'basic_cooling.define.cooling_enums'
local utils = require 'basic_cooling.cooling_utils'
local custom_msg = require 'messages.custom'
local privilege = require 'mc.privilege'
local f_enum = require 'class.types.types'
local data_keeping = require 'basic_cooling.data_keeping.external_data_keeping'
local fans_config = require 'basic_cooling.cooling_config.fans_config'
local pumps_config = require 'basic_cooling.cooling_config.pumps_config'
local sqlite3 = require 'lsqlite3'
local cjson = require 'cjson'
local cooling_group = require 'basic_cooling.cooling_device.cooling_group'

-- 最大可设置迟滞量
local MAX_HYSTERESIS<const> = 5

local cooling_policys = class()

function cooling_policys:ctor(db, base_service, persist)
    self.db = db
    self.base_service = base_service
    self.persist = persist
    self.objs = {} -- 调速policy对象数组
    self.type_obj_map = {}
    self.is_policy_updated = false
    self.fans_config_instance = fans_config.get_instance()
    self.pumps_config_instance = pumps_config.get_instance()
    self.cooling_group_instance = cooling_group.get_instance()
    self.data_keeping_objs = data_keeping.get_instance().data_keeping_objs
    self.hdd_backplane_data = self.data_keeping_objs.hdd_backplane_data
    self.cards_data = self.data_keeping_objs.cards_data
    self.system_fan_type = nil
end

-- 设置CoolingPolicy数据库数据
local function set_policy_db_prop(self, primary_key_value, prop_name, prop_value)
    local json_prop_value = prop_value
    if self.db.CoolingPolicy:persistences(prop_name) == 'protect_power_off' then
        if type(prop_value) == 'table' then
            json_prop_value = cjson.encode(prop_value)
        end
        self.persist:per_save(sqlite3.UPDATE, 't_cooling_policy', {{'PolicyIdx', primary_key_value}},
            {[prop_name] = {value = json_prop_value, persist_type = 'protect_power_off'}})
    end
end

function cooling_policys:get_objs()
    return self.objs
end

function cooling_policys:get_policy_by_id(id)
    return self.objs[id]
end

function cooling_policys:on_preprocess_cb(object)
    object[props.ORIGIN_POLICY_TEMP_RANGE_LOW] = object[props.POLICY_TEMP_RANGE_LOW]
    object[props.ORIGIN_POLICY_TEMP_RANGE_HIGHT] = object[props.POLICY_TEMP_RANGE_HIGHT]
    object[props.ORIGIN_POLICY_SPEED_RANGE_LOW] = object[props.POLICY_SPEED_RANGE_LOW]
    object[props.ORIGIN_POLICY_SPEED_RANGE_HIGH] = object[props.POLICY_SPEED_RANGE_HIGH]
    return true
end

function cooling_policys:obj_add_callback(class_name, object, position)
    log:debug('[Cooling] Add CoolingPolicy Id: ' .. object.PolicyIdx)

    self.objs[object.PolicyIdx] = object

    self:policy_added_action(object)

    object:get_mdb_object('bmc.kepler.Systems.CoolingPolicy')
        .property_before_change:on(function(name, value, sender)
            return self:policy_props_before_change_callback(name, value, sender)
        end)

    object:get_mdb_object('bmc.kepler.Systems.CoolingPolicy')
        .property_changed:on(function(name, value, sender)
            self:policy_props_changed_callback(object, name, value, sender)
        end)

    self.base_service:ImplCoolingPolicyCoolingPolicySetHysteresis(function(obj, ctx, ...)
        return self:SetHysteresis(obj, ctx, ...)
    end)
    self.base_service:ImplCoolingPolicyCoolingPolicySetCustomCoolingPolicy(function(obj, ctx, ...)
        return self:SetCustomCoolingPolicy(obj, ctx, ...)
    end)
end

function cooling_policys:policy_added_action(object)
    if object[props.POLICY_TYPE] ~= f_enum.PolicyType.InvalidType:value() then
        self.type_obj_map[object[props.POLICY_TYPE]] = object
    end

    if object[props.POLICY_TYPE] ~= 1 then -- 调速类型为非自定义则不进行持久化判断
        return
    end

    if not object[props.POLICY_TEMP_ARRAY] or #object[props.POLICY_TEMP_ARRAY] == 0 or
        not object[props.POLICY_SPEED_ARRAY] or #object[props.POLICY_SPEED_ARRAY] == 0 then
            return
        end
    log:notice('The temperature and fan speed arrays of policy(%s) are persisted', object[props.POLICY_IDX])
    local temp_str = object[props.POLICY_TEMP_ARRAY]
    local temp_arr_low = {string.byte(temp_str, 1, #temp_str)}
    -- temp low数组下限填-127
    table.insert(temp_arr_low, 1, -127)
    local temp_arr_high = {string.byte(temp_str, 1, #temp_str)}
    -- temp high数组上限填127
    table.insert(temp_arr_high, 127)

    object[props.POLICY_TEMP_RANGE_LOW] = temp_arr_low
    object[props.POLICY_TEMP_RANGE_HIGHT] = temp_arr_high
    object[props.POLICY_SPEED_RANGE_LOW] =
        {string.byte(object[props.POLICY_SPEED_ARRAY], 1, #object[props.POLICY_SPEED_ARRAY])}
    object[props.POLICY_SPEED_RANGE_HIGH] =
        {string.byte(object[props.POLICY_SPEED_ARRAY], 1, #object[props.POLICY_SPEED_ARRAY])}
end

function cooling_policys:policy_props_before_change_callback(name, value, sender)
    if name == props.POLICY_SPEED_RANGE then
        local arr = {string.byte(tostring(value), 1, 2)}
        if #value ~= 2 or arr[1] >= arr[2] then
            return false
        end
    end
    return true
end

function cooling_policys:policy_props_changed_callback(object, name, value, sender)
    if name == props.POLICY_TEMP_RANGE_LOW or name == props.POLICY_TEMP_RANGE_HIGHT or
        name == props.POLICY_SPEED_RANGE_LOW or name == props.POLICY_SPEED_RANGE_HIGH then
        self:set_policy_updated_flag(true)
    end
end

function cooling_policys:obj_delete_callback(class_name, object, position)
    log:notice('[Cooling] Delete CoolingPolicy Id: ' .. object.PolicyIdx)

    if object[props.POLICY_TYPE] ~= f_enum.PolicyType.InvalidType:value() and
        self.type_obj_map[object[props.POLICY_TYPE]] then
        self.type_obj_map[object[props.POLICY_TYPE]] = nil
    end

    if not self.objs[object.PolicyIdx] then
        log:error("[Cooling] Delete Policy but object is nil, CoolingPolicy Id: " .. object.PolicyIdx)
        return
    end

    self.objs[object.PolicyIdx] = nil
end

function cooling_policys:get_obj_by_policy_type(policy_type)
    return self.type_obj_map[policy_type]
end

local function get_low_temp_range(arr)
    -- temp low数组下限填-127
    local t = {-127}
    for i = 1, #arr do
        t[i + 1] = arr[i]
    end
    return t
end

local function get_high_temp_range(arr)
    local t = {}
    for i = 1, #arr do
        t[i] = arr[i]
    end
    -- temp high数组上限填127
    t[#arr + 1] = 127
    return t
end

local function is_smart_cooling_enabled(config_obj)
    return config_obj.SmartCoolingState == cooling_enums.smart_cooling_state.ENABLED
end

local function verify_para_for_set_custom_policy(obj, temp_arr, speed_arr)
    -- 自定义环温范围最大值60,自定义环温和转速数组长度最大值64
    local MAX_ENV_VAL<const>, MAX_ENV_LEN<const> = 60, 64
    -- 处理温度区间数组,长度最小为3
    if #temp_arr < 3 or #temp_arr >= MAX_ENV_LEN then
        log:error('Array length is invalid')
        return false, custom_msg.PropertyMemberQtyExceedLimit('%TemperatureArray')
    end
    -- 处理风扇转速数组,长度最小为4
    if #speed_arr <= 3 or #speed_arr >= MAX_ENV_LEN then
        log:error('Array length is invalid')
        return false, custom_msg.PropertyMemberQtyExceedLimit('%FanSpeedArray')
    end

    -- 不支持进风口温度设置
    if not obj[props.POLICY_CUSTOM_SUPPORTED] then
        log:error('Cooling policy(idx:%u) dose not support custom', obj[props.POLICY_IDX])
        return false, custom_msg.PropertyModificationNotSupported('%TemperatureArray')
    end
    -- 参数合法性判断:非升序排列
    if not utils.is_array_ascending(temp_arr) then
        log:error('Temp array must be in ascending order')
        return false, custom_msg.InvalidTemperatureRange('%TemperatureArray')
    end
    if temp_arr[1] < 0 then
        log:error('Temp arr out of range:[0 60]')
        return false, custom_msg.PropertyValueOutOfRange(temp_arr[1], '%TemperatureArray')
    end
    -- 温度区间不满足[0,60]范围
    if temp_arr[#temp_arr] > MAX_ENV_VAL then
        log:error('Temp arr out of range:[0 60]')
        return false, custom_msg.PropertyValueOutOfRange(temp_arr[#temp_arr], '%TemperatureArray')
    end
    -- 风扇转速数组不满足升序条件
    if not utils.is_array_not_descending(speed_arr) then
        log:error('Speed array cannot be descended')
        return false, custom_msg.InvalidFanSpeed('%FanSpeedArray')
    end
    -- 风扇转速不满足[20%,100%]范围
    local speed_range = obj[props.POLICY_SPEED_RANGE]
    speed_range = type(speed_range) == 'string' and {string.byte(speed_range, 1, #speed_range)} or speed_range
    if speed_arr[1] < speed_range[1] then
        log:error('Speed arr out of range:[%u %u]', speed_range[1], speed_range[2])
        return false, custom_msg.PropertyValueOutOfRange(speed_arr[1], '%FanSpeedArray[0]')
    end
    if speed_arr[#speed_arr] > speed_range[2] then
        log:error('Speed arr out of range:[%u %u]', speed_range[1], speed_range[2])
        return false,
            custom_msg.PropertyValueOutOfRange(speed_arr[#speed_arr], '%FanSpeedArray[' .. #speed_arr - 1 .. ']')
    end
    -- 不满足转速数组大小比温度大1
    if (#speed_arr - #temp_arr) ~= 1 then
        log:error('Envrange array size less than speed array size is not 1')
        return false, custom_msg.WrongValueQty('%TemperatureArray', '%FanSpeedArray')
    end
    return true, nil
end

function cooling_policys.set_cooling_policy(policy_o, db, TemperatureArray, FanSpeedArray)
    local temp_arr = {string.byte(TemperatureArray, 1, #TemperatureArray)}
    local speed_arr = {string.byte(FanSpeedArray, 1, #FanSpeedArray)}

    local ok, err_info = verify_para_for_set_custom_policy(policy_o, temp_arr, speed_arr)
    if not ok then
        return ok, err_info
    end

    policy_o[props.POLICY_TEMP_RANGE_LOW] = get_low_temp_range(temp_arr)
    policy_o[props.POLICY_TEMP_RANGE_HIGHT] = get_high_temp_range(temp_arr)
    policy_o[props.POLICY_SPEED_RANGE_LOW] = speed_arr
    policy_o[props.POLICY_SPEED_RANGE_HIGH] = speed_arr

    local t_cooling_policy = db.CoolingPolicy({PolicyIdx = policy_o.PolicyIdx})
    t_cooling_policy['TemperatureArray'] = TemperatureArray
    t_cooling_policy['FanSpeedArray'] = FanSpeedArray
    t_cooling_policy:save()

    return true, nil
end

-- 根据当前调速器件的调速模式来判断
local function cooling_ctrl_mode_condition(policy_o, fans_conf_o, pumps_config_o)
    if fans_conf_o and
        fans_conf_o[props.CTL_MODE] == cooling_enums.modes.Manual and
        policy_o.CoolingMedium == props.COOLING_AIR then
        return false
    end
    if pumps_config_o and -- 先判断对象是否存在
        pumps_config_o[props.CTL_MODE] == cooling_enums.modes.Manual and
        policy_o.CoolingMedium == props.COOLING_LIQUID then
        return false
    end
    return true
end

-- 根据SmartCoolingMode生效
local function smart_cooling_mode_condition(policy_o)
    if string.len(policy_o.ExpCondVal) ~= 0 and policy_o.ExpCondVal ~= policy_o.ActualCondVal then
        return false
    end
    return true
end

-- 根据风扇类型来生效
local function fan_type_condition(self, policy_o)
    local fan_type = policy_o.FanType
    if #fan_type == 0 then
        return true
    end
    if not self.system_fan_type then
        self.system_fan_type = self.cooling_group_instance:get_system_fan_type()
    end
    for _, expect_fan_type in pairs(fan_type) do
        -- 一种满足即可
        if self.system_fan_type == expect_fan_type then
            return true
        end
    end
    return false
end

-- 根据硬盘背板生效
local function disk_temp_condition(self, policy_o, conf_o, disk_temp_arr)
    if #disk_temp_arr == 0 then
        return true
    end
    -- 存在硬盘温度获取失败才生效   true硬盘温度都能获取
    if policy_o.DiskTempUnavailableToValid and conf_o.DiskRowTemperatureAvailable then
        return false
    end
    for _, name in pairs(disk_temp_arr) do
        if self.hdd_backplane_data:is_hdd_backplane_loaded(name) then
            return true
        end
    end
    return false
end

-- 根据pcie卡来识别
local function pcie_card_name_condition(self, pcie_card_names)
    if #pcie_card_names == 0 then
        return true
    end
    for _, name in pairs(pcie_card_names) do
        if self.cards_data:is_pciecard_loaded(name) then
            return true
        end
    end
    return false
end

-- 调速Policy是否有效检测,若配置多个条件需都满足Policy才生效
local function policy_valid_detect(self, policy_o, fans_conf_o, pumps_config_o)
    if cooling_ctrl_mode_condition(policy_o, fans_conf_o, pumps_config_o) and
        smart_cooling_mode_condition(policy_o) and
        fan_type_condition(self, policy_o) and
        disk_temp_condition(self, policy_o, fans_conf_o, policy_o.HDDBackPlaneName) and
        disk_temp_condition(self, policy_o, fans_conf_o, policy_o.HDDRearBackPlaneName) and
        pcie_card_name_condition(self, policy_o.PCIeCardName) then
        return true
    end
    return false
end

function cooling_policys:get_policy_updated_flag()
    return self.is_policy_updated
end

function cooling_policys:set_policy_updated_flag(flag)
    self.is_policy_updated = flag
end

-- 更新调速曲线生效状态
function cooling_policys:update_policy_isvalid(data, requirement_obj, fans_config_o)
    -- 通过Requirement对象找到CoolingArea下的policy
    local req_base_id = requirement_obj[props.REQ_BASE_ID]
    local area_id = data.requirement_area_map[req_base_id]
    -- 通过Requirement的baseid无法匹配到Area,即Area对象中无requirementidx为baseid的
    if not area_id then
        log:debug('The CoolingRequirement(0x%x) can not match CoolingArea(RequirementIdx:%s)',
            requirement_obj[props.REQUIREMENT_ID], req_base_id)
        return
    end
    local area_obj = data.cooling_areas[area_id]
    for _, policy_id in pairs(area_obj[props.POLICY_IDX_GROUP]) do
        -- 曲线的生效条件为当前温度点生效且policy配置的条件都满足
        local policy_o = self.objs[policy_id]
        -- 漏配置policy或者area中配置了policy索引
        if not policy_o then
            log:debug('The CoolingPolicy(PolicyIdx:%s) is not exist', policy_id)
            goto continue
        end
        local cur_valid_stat = (requirement_obj[props.IS_VALID] == 1) and
            policy_valid_detect(self, policy_o, fans_config_o, pumps_config)
        if cur_valid_stat and policy_o[props.IS_VALID] == 0 then
            policy_o[props.IS_VALID] = 1
            log:notice('[IsValid] Policy(Id:%u) change to valid', policy_id)
            self:set_policy_updated_flag(true)
        elseif not cur_valid_stat and policy_o[props.IS_VALID] == 1 then
            policy_o[props.IS_VALID] = 0
            log:notice('[IsValid] Policy(Id:%u) change to invalid', policy_id)
            self:set_policy_updated_flag(true)
        end
        ::continue::
    end
end

function cooling_policys:get_policy_isvalid(policy_o, medium, smart_mode)
    local fans_conf_o = self.fans_config_instance:get_obj()
    if policy_o[props.COOLING_POLICY_MEDIUM] == medium and
        policy_o.ExpCondVal == smart_mode and
        fan_type_condition(self, policy_o) and
        disk_temp_condition(self, policy_o, fans_conf_o, policy_o.HDDBackPlaneName) and
        disk_temp_condition(self, policy_o, fans_conf_o, policy_o.HDDRearBackPlaneName) and
        pcie_card_name_condition(self, policy_o.PCIeCardName) then
        return true
    end
    return false
end

-- 设置迟滞量
function cooling_policys:SetHysteresis(obj, ctx, hysteresis)
    if hysteresis < 0 or hysteresis > MAX_HYSTERESIS then
        log:error('CoolingPolicy(id: %s) %s is out of range, expect 0-5', obj[props.POLICY_IDX], 'Hysteresis')
        error(custom_msg.ValueOutOfRange('%Hysteresis'))
    end
    if hysteresis == obj[props.HYSTERESIS] then
        set_policy_db_prop(self, obj[props.POLICY_IDX], props.HYSTERESIS, hysteresis)
    else
        obj[props.HYSTERESIS] = hysteresis
    end
    self:set_policy_updated_flag(true)
end

local function getSpeed(temp, temp_low, temp_high, speed_low, speed_high)
    -- 遍历温度范围，找到对应的属性
    for i = 1, #temp_low, 1 do
        -- 根据调速策略配置规则，左闭右开
        if temp >= temp_low[i] and temp < temp_high[i] then
            -- 计算转速
            local Ratio = (speed_high[i] - speed_low[i]) / (temp_high[i] - temp_low[i])
            local speed = Ratio * (temp - temp_low[i]) + speed_low[i]
            return speed
        end
        -- 存在某个温度下计算不出转速直接退出
        if temp < temp_low[i] then
            return nil
        end
    end
end

-- 新曲线的每个温度对应的转速需要大于等于CSR曲线对应每个温度的转速
local function check_speed_is_not_dowm(import_obj, source_tree_obj)
    local old_speed, new_speed
    -- 对-127到127的每个温度下的转速进行对比校验
    for temp = -127, 126, 1 do
        old_speed = getSpeed(temp,
            source_tree_obj[props.ORIGIN_POLICY_TEMP_RANGE_LOW], source_tree_obj[props.ORIGIN_POLICY_TEMP_RANGE_HIGHT],
            source_tree_obj[props.ORIGIN_POLICY_SPEED_RANGE_LOW], source_tree_obj[props.ORIGIN_POLICY_SPEED_RANGE_HIGH])
        new_speed = getSpeed(temp,
            import_obj.temp_low, import_obj.temp_high,
            import_obj.pwm_low, import_obj.pwm_high)
        if new_speed == nil then
            log:error('CoolingPolicy(id: %s) is invalid, ' ..
                'the speed of the imported policy is none when the temperature is %s',
                import_obj.policy_idx, temp)
            return false
        end
        if new_speed < old_speed then
            log:error('CoolingPolicy(id: %s) is invalid, ' ..
                'the speed of the imported policy is smaller than the original policy when the temperature is %s, ' ..
                'old_policy_speed: %s, new_policy_speed: %s', import_obj.policy_idx, temp, old_speed, new_speed)
            return false
        end
    end
    return true
end

local function set_non_customized_policy(ctx, source_tree_obj, TemperatureArray, FanSpeedArray)
    local temp_arr = {string.byte(TemperatureArray, 1, #TemperatureArray)}
    local speed_arr = {string.byte(FanSpeedArray, 1, #FanSpeedArray)}

    local import_obj = {}
    import_obj.policy_idx = source_tree_obj[props.POLICY_IDX]
    import_obj.temp_low = get_low_temp_range(temp_arr)
    import_obj.temp_high = get_high_temp_range(temp_arr)
    import_obj.pwm_low = speed_arr
    import_obj.pwm_high = speed_arr
    local ok = check_speed_is_not_dowm(import_obj, source_tree_obj)
    if not ok then
        utils.op(ctx, 'Set CoolingPolicy(id:%u) failed', import_obj.policy_idx)
        error(custom_msg.InvalidFanSpeed('%FanSpeedArray'))
    end

    source_tree_obj[props.POLICY_TEMP_RANGE_LOW] = get_low_temp_range(temp_arr)
    source_tree_obj[props.POLICY_TEMP_RANGE_HIGHT] = get_high_temp_range(temp_arr)
    source_tree_obj[props.POLICY_SPEED_RANGE_LOW] = speed_arr
    source_tree_obj[props.POLICY_SPEED_RANGE_HIGH] = speed_arr
    utils.op(ctx, 'Set CoolingPolicy(id:%u) successfully', import_obj.policy_idx)
end

-- 设置用户自定义环温曲线
function cooling_policys:SetCustomCoolingPolicy(obj, ctx, TemperatureArray, FanSpeedArray)
    -- 设置非自定义曲线
    if not obj[props.POLICY_CUSTOM_SUPPORTED] then
        local source_tree_obj = self:get_policy_by_id(obj[props.POLICY_IDX])
        set_non_customized_policy(ctx, source_tree_obj, TemperatureArray, FanSpeedArray)
    else
        local ok, err_info =
            self.set_cooling_policy(obj, self.db, TemperatureArray, FanSpeedArray)
        if not ok then
            utils.op(ctx, 'Set custom cooling policy(id:%u) failed', obj[props.POLICY_IDX])
            error(err_info)
        end

        local temp_arr = {string.byte(TemperatureArray, 1, #TemperatureArray)}
        local speed_arr = {string.byte(FanSpeedArray, 1, #FanSpeedArray)}

        utils.op(ctx, 'Set custom cooling policy(id:%u) successfully, temperature array[%s], speed array[%s]',
            obj[props.POLICY_IDX], table.concat(temp_arr, ' '), table.concat(speed_arr, ' '))
    end
    self:set_policy_updated_flag(true)
end

function cooling_policys:get_custom_inlet_policy()
    for _, obj in pairs(self.objs) do
        if obj.PolicyType == f_enum.PolicyType.InletCustom:value() and obj[props.POLICY_CUSTOM_SUPPORTED] then
            return obj
        end
    end
    return nil
end

function cooling_policys:get_custom_inlet_policys()
    local objs = {}
    for _, obj in pairs(self.objs) do
        if obj.PolicyType == f_enum.PolicyType.InletCustom:value() and obj[props.POLICY_CUSTOM_SUPPORTED] then
            table.insert(objs, obj)
        end
    end
    return objs
end

function cooling_policys:get_custom_policy()
    local custom_policys = {}
    for _, obj in pairs(self.objs) do
        if obj.PolicyType ~= f_enum.PolicyType.InvalidType:value() and obj[props.POLICY_CUSTOM_SUPPORTED] then
            table.insert(custom_policys, obj)
        end
    end
    return custom_policys
end

return Singleton(cooling_policys)